package com.vhall.component.plugin.common.aspect;

import com.vhall.component.plugin.common.annotation.RedisSpinLock;
import com.vhall.component.plugin.common.utils.RedisLocker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.RandomStringGenerator;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;

import static org.apache.commons.text.CharacterPredicates.DIGITS;
import static org.apache.commons.text.CharacterPredicates.LETTERS;

/**
 * redis分布式锁切面处理（自旋处理）
 * 增加前缀，可yaml配置,不填使用默认
 *
 * @author yuanzh
 */
@Component
@Aspect
@Slf4j
@Order(1001)
public class RedisSpinLockAspect {

    @Value("${redis-lock.key-prefix?:vhall:comp}")
    private String keyPrefix;

    private final ExpressionParser parser = new SpelExpressionParser();
    private final LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();

    @Around(value = "execution(public * com.vhall.component..*.*(..)) && @annotation(redisLock)")
    public Object lock(ProceedingJoinPoint pjp, RedisSpinLock redisLock) throws Throwable {
        String spelKey = StringUtils.isBlank(redisLock.value()) ? redisLock.redisKey() : redisLock.value();
        Method method = this.getMethod(pjp);

        // 获取方法的参数值
        Object[] args = pjp.getArgs();
        EvaluationContext context = this.bindParam(method, args);
        //根据spel表达式获取值
        Expression expression = parser.parseExpression(spelKey);
        //生成redisKey
        String redisLockKey = keyPrefix(pjp, redisLock) + expression.getValue(context);
        RandomStringGenerator generator = new RandomStringGenerator.Builder().withinRange('0', 'z').filteredBy(LETTERS, DIGITS)
                .build();
        String redisValue = generator.generate(10);
        if (!RedisLocker
                .lock(redisLockKey, redisValue, redisLock.retryTimeout(), redisLock.expireTime(), redisLock.retry(), 0, 0)) {
            try {
                Thread.sleep(redisLock.sleepTime());
            } catch (InterruptedException e) {
                log.error("Interrupted!", e);
                Thread.currentThread().interrupt();
            }
            return lock(pjp, redisLock);
        }
        try {
            return pjp.proceed();
        } finally {
            RedisLocker.unlock(redisLockKey, redisValue);
        }
    }

    private String keyPrefix(ProceedingJoinPoint pjp, RedisSpinLock redisLock) {
        if (StringUtils.isNotBlank(redisLock.prefix())) {
            return redisLock.prefix() + ":";
        }
        String className = pjp.getSignature().getDeclaringType().getSimpleName();
        String methodName = pjp.getSignature().getName();
        return keyPrefix + ":" + className + ":" + methodName + ":";
    }

    /**
     * 获取当前执行的方法
     *
     * @param pjp
     * @return
     * @throws NoSuchMethodException
     */
    private Method getMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        return pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());
    }

    /**
     * 将方法的参数名和参数值绑定
     *
     * @param method 方法，根据方法获取参数名
     * @param args   方法的参数值
     * @return
     */
    private EvaluationContext bindParam(Method method, Object[] args) {
        //获取方法的参数名
        String[] params = discoverer.getParameterNames(method);

        //将参数名与参数值对应起来
        EvaluationContext context = new StandardEvaluationContext();

        if (Objects.nonNull(params) && params.length > 0) {
            for (int len = 0; len < params.length; len++) {
                context.setVariable(params[len], args[len]);
            }
        }
        return context;
    }

}
